﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Flexible.Implementions.Attributes;
using Flexible.Interfaces;

namespace Flexible.Framework
{
    class PluginManager
    {
        ISettingsStreamer settingsStreamer;
        public PluginManager(ISettingsStreamer settingsStreamer)
        {
            this.settingsStreamer = settingsStreamer;
        }

        Dictionary<string, PluginActivator> registry = new Dictionary<string, PluginActivator>();
        List<string> autostartList = new List<string>();

        internal IEnumerable<PluginActivator> SearchAssembly(Assembly assembly)
        {
            if (assembly == null)
                throw new ArgumentNullException("assembly should not be null", "assembly");
            if (!assembly.IsDefined(typeof(ContainsPluginsAttribute), false))
                throw new ArgumentException(string.Format("ContainsPluginAttribute not found in assembly {0}.", assembly.FullName), "assembly");

            List<PluginActivator> result = new List<PluginActivator>();
            foreach (Type t in assembly.GetTypes())
            {
                if (t.IsClass && !t.IsAbstract && !t.IsGenericType && t.IsDefined(typeof(PluginAttribute), true))
                {
                    PluginActivator activator = new PluginActivator(t);
                    result.Add(activator);
                }
            }
            return result;
        }

        internal void Register(PluginActivator activator)
        {
            string id = activator.PluginId;
            registry.Add(id, activator);
        }

        internal void LoadAutostartList(Stream stream)
        {
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            autostartList = new List<string>();
            for (string line = reader.ReadLine(); line != null; line = reader.ReadLine())
            {
                line = line.Trim();
                if (line.Length > 0)
                {
                    autostartList.Add(line);
                }
            }
        }

        internal void SaveAutostartList(Stream stream)
        {
            StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
            foreach (string s in autostartList)
            {
                writer.WriteLine(s);
            }
        }

        internal void Run()
        {
            List<IApplication> applications = new List<IApplication>();
            foreach (string pluginId in autostartList)
            {
                PluginActivator activator = registry[pluginId];
                Activate(activator);
                if (activator.IsApplication)
                    applications.Add((IApplication)activator.Plugin);
            }

            foreach (var app in applications)
            {
                if (!app.Run())
                    break;
            }

            foreach (PluginActivator activator in registry.Values)
            {
                if (activator.Enabled)
                {
                    Deactivate(activator);
                }
            }
        }

        private void Deactivate(PluginActivator activator)
        {
            PluginActivateContext context = GetActivateContext(activator);

            foreach (PluginActivator attached in GetAttachments(activator))
            {
                Deactivate(attached);
            }

            foreach (PluginActivator observer in GetObservers(activator))
            {
                observer.Plugin.InsterestedPluginDisabling(activator.PluginId, activator.Plugin);
            }

            using (Stream output = settingsStreamer.GetWriteStream(activator.Plugin.Signature))
            {
                activator.Deactivate(context, output);
            }
        }

        private void Activate(PluginActivator activator)
        {
            PluginActivateContext context = GetActivateContext(activator);

            foreach (PluginActivator requirement in context.Requirements.Values)
            {
                Activate(requirement);
            }

            using (Stream input = settingsStreamer.GetReadStream(activator.Plugin.Signature))
            {
                activator.Activate(context, input);
            }

            foreach (PluginActivator observer in GetObservers(activator))
            {
                activator.Plugin.InsterestedPluginEnabled(activator.PluginId, activator.Plugin);
            }
        }

        private PluginActivateContext GetActivateContext(PluginActivator activator)
        {
            PluginActivateContext context = new PluginActivateContext();

            //activate requirements
            foreach (PluginActivator requirement in GetRequirements(activator))
            {
                context.Requirements.Add(requirement.PluginId, requirement);
            }

            //gather interestes
            foreach (PluginActivator interest in GetInterests(activator))
            {
                if (interest != null)
                    context.Interests.Add(interest.PluginId, interest);
            }
            return context;
        }

        private IEnumerable<PluginActivator> GetObservers(PluginActivator activator)
        {
            foreach (var test in registry.Values)
            {
                foreach (var query in test.Plugin.Signature.Interests)
                {
                    if (query.IsMatch(activator.Plugin.Signature))
                        yield return test;
                }
            }
        }

        private IEnumerable<PluginActivator> GetInterests(PluginActivator activator)
        {
            foreach (var query in activator.Plugin.Signature.Interests)
            {
                foreach (var test in registry.Values)
                    if (query.IsMatch(test.Plugin.Signature))
                        yield return test;
            }
        }

        private IEnumerable<PluginActivator> GetAttachments(PluginActivator activator)
        {
            foreach (var test in registry.Values)
            {
                foreach (var query in test.Plugin.Signature.Requirements)
                {
                    if (query.IsMatch(activator.Plugin.Signature))
                        yield return test;
                }
            }
        }

        private IEnumerable<PluginActivator> GetRequirements(PluginActivator activator)
        {
            foreach (var query in activator.Plugin.Signature.Requirements)
            {
                foreach (var test in registry.Values)
                    if (query.IsMatch(test.Plugin.Signature))
                        yield return test;
            }
        }
    }
}
